#!/usr/bin/python
import actory, factory, random, math, team, task, sys, joeread, pprint, bore, utility

from team import *
from task import *
from utility import *
from pprint import pprint

def randpri(a=1, b=10):
    return random.randint(a,b)

def randpay(a=10,b=40):
    return random.randint(a,b)

def undiscretize(s):
    if (s == 1): newval = randpay(10,19)
    elif (s == 2): newval = randpay(20, 29)
    elif (s == 3): newval = randpay(30, 40)
    else: newval = -1
    
    return newval
    
def undiscretizePri(s):
    if (s == 1): newval = randpri(1,3)
    elif (s == 2): newval = randpri(4,7)
    elif (s == 3): newval = randpri(8,10)
    else: newval = -1
    
    return newval


class POM(actory.World  ):
    def __init__(self,printflag,late):
        super(POM,self).__init__(printflag)

        "Project Manager has teams and tasks"
        self.P = ProjectManager()
        self.P.initTeams()
        self.P.initTasks()
        self.late = late
        
        "A signal which the Assigner Machine watches for"
        self.assignersignal = False

        "The Performance Metrics"
        self.s1 = 0.0; #MoneySpent/LOC [Minimized]
        self.s2 = 0.0; #Days/LOC [Minimized]
        self.s3 = 0.0; #Money Spent [Minimized]
        self.s4 = 0.0; #Days [Minimized]
        self.s5 = 0.0; #MoneySpent/Day [Minimized]
        self.s6 = 0.0; #Average Idle Time of a Team [Minimized]
        self.scores = []
        
        "Input Params"
        self.input = []
        self.transitions = []
        
    "flag getter"
    def assignerSignal(self):
        return self.assignersignal

    def noAssignerSignal(self):
        return not self.assignersignal

    "flag setter"
    def signalAssigner(self):
        self.assignersignal = True

    def newTeam(self):

        "only add a new team at a late phase in the game"
        "and if there aren't more than 7 teams"
        "late = 50% or beyond"

        if ((self.calcProgress() > self.late) and (len(self.P.teams) <= 7)):    
            newName = "New Team " + str(len(self.P.teams))
            self.P.teams.append(Team(newName, None, True, random.randint(20, 40)))
            i = len(self.P.teams)-1
            newTeamMachine =  actory.Easy("""
                t0    | ?haveTask("""+str(i)+""")   |                              | Team has a task to work on        | t1
                       | ?noTask("""+str(i)+""")     | ?idle("""+str(i)+""")        | Team is idle                      | t0
                t1    | ?taskDone("""+str(i)+""")   | ?finishTask("""+str(i)+""")  | Team announces completion of task | t0
                       | ?tasknotDone("""+str(i)+""")| ?doWork("""+str(i)+""")      | Team is working on task           | t1
            """)
            newTeamMachine.name = newName
            newTeamMachine.priority = random.randint(1,10)
            f.add_machine(newTeamMachine)
        
    def projectDone(self):
        return (self.tasksLeft() == 0)

    def calcProgress(self):
        tasksDone = 0
        tasksUndone = 0
        for i in self.P.tasks:
            if i.done: tasksDone+= 1
            else: tasksUndone+= 1
        return float(float(tasksDone)/float(tasksDone+tasksUndone))
          
    def projectStatus(self):       
        amtdone = int(100 * self.calcProgress())
        cprint ("\nProject Completion Guage: " + "[" + "#"*(40*amtdone/100) + "-"*int(40*(1-float(float(amtdone)/100))) + "]" +  str(amtdone) + "%\n")

    def tasksLeft(self):
        cnt = 0
        for i in self.P.tasks:
            if not i.done: cnt = cnt +1
        return cnt
    
    def idleTeams(self):
        for i in self.P.teams:
            if i.idle: return True
        return False

    def allBusy(self):
        for i in self.P.teams:
            if i.idle: return False
        return True

    def doableTasks(self):
        "checks if there are any doable tasks"
        "doable is defined as:"
        "  - within budget"
        "  - an available team can be assigned to an available task"
        "  - available team is an idle team"
        "  - available task has all requirements satisfied"
        for i in self.P.tasks:
            if not (i.done) and self.checkRequirements(i.req):
                for j in self.P.teams:
                    if j.idle:
                        if math.ceil(i.hard/j.ability)*i.cost <= self.P.budget:
                            return True
        return False

    def nodoableTasks(self):
        return not self.doableTasks()
    
    def checkRequirements(self, r):
        "check through requirements and make sure each task is completed"
        if r:
            for i in range(len(r)):
                if not r[i].done: return False
        return True                
        
    def assignTasks(self):
        "assign tasks to each team as possible"
        "randomize the tasks"
        random.shuffle(self.P.tasks)
        for i in range(len(self.P.teams)):
            if self.P.teams[i].idle:
                for j in range(len(self.P.tasks)):
                    if not self.P.tasks[j].done and not self.P.tasks[j].team and self.checkRequirements(self.P.tasks[j].req):
                        if math.ceil(self.P.tasks[j].hard/self.P.teams[i].ability)*self.P.tasks[j].cost <= self.P.budget:
                            self.P.assignTask(self.P.teams[i], self.P.tasks[j])
                            break
        self.assignersignal = True

    def outofmoney(self):
        "cannot do the cheapest thing, or everyone's idle but can't do anything"
        return not (self.projectDone()) and ((self.P.budget < self.cheapestTask()) or (not self.allBusy() and self.nodoableTasks()))

    def ranoutofmoney(self):
        cprint ("Project Ended Incomplete - Ran out of Money.  Remaining Budget = " + str(self.P.budget))

    def workLeft(self):
        return not self.projectDone()
    
    def cheapestTask(self):
        low = 999999
        for i in range(len(self.P.tasks)):
            if self.P.tasks[i].cost < low: low = self.P.tasks[i].cost
        return low
    
    def haveTask(self, i):
        return self.P.teams[i].haveTask()

    def taskDone(self, i):
        return self.P.teams[i].taskDone()

    def tasknotDone(self, i):
        return self.P.teams[i].tasknotDone()

    def noTask(self, i):
        return self.P.teams[i].noTask()

    def finishTask(self, i):
        self.P.teams[i].finishTask()

    def doWork(self, i):
        price = 0
        
        self.P.teams[i].doWork()
        self.P.days+= 1
        
        "pay the worker"
        price += self.P.teams[i].pay
        
        "cost of work"
        price += self.P.teams[i].task.cost

        self.P.budget -= price
        self.P.moneyspent += price
        
        "status report"
        cprint ("*"*self.P.teams[i].task.days + " Team [" + self.P.teams[i].name + "] Reports: " + str(max(0, self.P.teams[i].task.hard)) + " Effort Remaining on Task [" + self.P.teams[i].task.name + "]")

        if self.P.teams[i].task.hard <= 0:
            cprint ("The following task was completed: " + self.P.teams[i].task.name)
            
    def projectEnd(self):
        cprint ("Project Finished.  Total Cost: " + str(self.P.moneyspent))
        cprint ("Total Days Spent: " + str(self.P.days))
        totalidle = 0
        totald = 0
        
        for i in self.P.teams:
            if (i.daysWorked + i.daysIdle) > 0:
                cprint ("[Team] " + i.name + ". Idle %: " + str(float(i.daysIdle)/float(i.daysIdle+i.daysWorked)*100))
                totalidle+= i.daysIdle
                totald+= (i.daysIdle+i.daysWorked)
                
        avgIdle = float(totalidle)/float(totald)
        cprint ("average % idle: " + str(avgIdle*100))
            
        totalloc = 0
        for i in self.P.tasks:
            totalloc+= i.loc

        cprint ("totalloc: " + str(totalloc))
        cprint ("Money per loc: " + str(float(float(self.P.moneyspent)/float(totalloc))))
        cprint ("Days per kloc: " + str(float(float(self.P.days)/float(totalloc))))

        newlist = []
        priorities = []
        self.scores = []
        
        "Performance Metrics of a Project"
        self.s1 = float(self.P.moneyspent)/float(totalloc)
        self.s2 = 1000.0 * float(self.P.days)/float(totalloc)
        self.s3 = self.P.moneyspent
        self.s4 = self.P.days
        self.s5 = float(self.P.moneyspent)/float(self.P.days)
        self.s6 = avgIdle
        self.scores.append(self.s1)
        self.scores.append(self.s2)
        self.scores.append(self.s3)
        self.scores.append(self.s4)
        self.scores.append(self.s5)
        self.scores.append(self.s6)

        for i in self.P.teams:
            priorities.append(i.pay)
            self.input.append(i.pay)
        
        for i in transition_priorities:
            self.transitions.append(i)


    def idle(self, i):
        "half pay rate for doing nothing"
        self.P.teams[i].goidle()
        self.P.budget = self.P.budget - 10

class ProjectManager(object):
    "project manager is the central processing unit of the software development process"
    "the PM has a collection of teams and tasks, and a budget"
    "if the budget is decreased to zero, the project must halt (projectActive)"
    def __init__(self):
        self.teams = []
        self.tasks = []
        self.budget = 1000000000000
        self.moneyspent = 0
        self.projectActive = True
        self.days = 0
        
    def initTeams(self):
        names = ["Delta Team", "Gamma Team", "Kappa Team", "Sigma Team", "Omega Team"]
        
        self.teams = []
        for i in names:
            self.teams.append(Team(i, None, True, random.randint(500, 500)))
        
    def initTasks(self):
        #generate a random list of tasks
        tasks = []

        NUM_TASKS = 64
        LOW_COST,HIGH_COST = 1500,1500
        LOW_HARD,HIGH_HARD = 1000,1000
        REQ_PROB = 0
            
        self.tasks = []
        task_number = 0 #numTasks
        for i in range (NUM_TASKS):
            newTask = Task("Task #"+str(i), random.randint(LOW_COST, HIGH_COST), False, None, [], random.randint(LOW_HARD, HIGH_HARD), 0)
            task_number += 1
            cnt = 0 #numReqs
            last = i-1-cnt
            newTask.req = []
            while True:
                if (random.randint(1,100) < (REQ_PROB*100)) and ((last) > 0):
                    e = random.randint(0, last)
                    last = e-1 #next highest possible req
                    newTask.req.append(self.tasks[e])
                    cnt = cnt + 1
                else: break
            self.tasks.append(newTask)
        
    def assignTask(self, team, task):
        team.assignTask(task)
        task.assignTeam(team)
        cprint ("A Task has been assigned:")
        "team.repr2()"
        
    def useMoney(self, amt):
        self.budget = self.budy - amt
        if self.budget <= 0: self.projectActive = False


#def pom_demo(late = 0.50, m = [randpri(), randpri(), randpri(), randpri(), randpri(), randpri(), randpri()]):
def pom_demo(late = 0.50, m = [randpay(), randpay(), randpay(), randpay(), randpay()]):
  
    global f
    f = factory.Factory(PRINT_FLAG)
    
    universe = POM(PRINT_FLAG, late)
    
    #adjust the pay rates as supplied by m1,m2,...,m5
    for i,v in enumerate(universe.P.teams):
        v.adjustPay(m[i])
        #v.adjustPay(randpay())
        
    
    #Build the Project Manager Machine
    PM = actory.Easy("""
        pm0    | ?projectDone()  | ?projectEnd()      | PM Announces Project Completion | pm.
               | ?outofmoney()   | ?ranoutofmoney()   | Project Halt                    | _ph.
               | ?workLeft()     | ?projectStatus()   | We Still have Work to do        | pm1
       pm1     | ?idleTeams()    |                    | PM sees some Teams are idle     | pm2
               | ?allBusy()      | ?newTeam()         | Everyone is busy                | pm0
       pm2     | ?doableTasks()  | ?signalAssigner()  | PM alerts the Assigner       | pm1
               | ?nodoableTasks()|                    | No available tasks to assign yet| pm0
    """)
    PM.name = "Project Manager"
    #PM.priority = m[0]
    PM.priority = 10
    universe.transitions.append(PM.priority)
        
    #Build Each Team Machine (5 Teams)
    team_machines = []
    for i in range(len(universe.P.teams)):
        team_machines.append( actory.Easy("""
            t0    | ?haveTask("""+str(i)+""")   |                              | Team has a task to work on        | t1
                   | ?noTask("""+str(i)+""")     | ?idle("""+str(i)+""")        | Team is idle                      | t0
            t1    | ?taskDone("""+str(i)+""")   | ?finishTask("""+str(i)+""")  | Team announces completion of task | t0
                   | ?tasknotDone("""+str(i)+""")| ?doWork("""+str(i)+""")      | Team is working on task           | t1
        """) )
        team_machines[i].name = universe.P.teams[i].name
        #team_machines[i].priority = m[i+1]
        team_machines[i].priority = 10
        universe.transitions.append(team_machines[i].priority)

    #Build the Assigner Machine
    assigner = actory.Easy("""
        #as0     | ?noAssignerSignal() |                  | Assigner is Idle | #as0
                 | ?assignerSignal()   | ?assignTasks()   | Assigner Working | #as0
    """)
    assigner.name = "The Assigner"
    #assigner.priority = m[6]
    assigner.priority = 10
    universe.transitions.append(assigner.priority)

    #Add Each machine to the factory
    f.add_machine(PM)
    f.add_machine(assigner)
    for i in team_machines:
        f.add_machine(i)

    #Run the FSM
    f.run(universe, 100000)

    #Print and return project performance scores
    cprint(universe.scores)
    return universe.scores, universe.input



def cprint(stuff):
    if PRINT_FLAG:
        print stuff



    
def brookeslaw():
    
    #-
    #this function is broken at the moment
    #-
    
    data = []
    S = [i/100.0 for i in xrange(0,101,1)]
    for e in S:
        datum = []
        datum.append(e)
        print "Brooke's Law (" + str(e) + ")"
        days = []
        for i in range(35):
            daystaken,s1,s2,s3,s4 = pom_demo(e)
            days.append(daystaken)
        datum.append(avg(days))
        print "Avg Days taken: ", str(datum[1])
        data.append(datum)

    fout = open("brookes.dat", "w")
    for i in data:
        fout.write(str(i))
        fout.write("\n")
    fout.close()

def avgDaysTest(numTrials, m1=randpri(),m2=randpri(),m3=randpri(),m4=randpri(),m5=randpri(),m6=randpri(),m7=randpri()):
    
    #Initialize matrix of scores | trials
    scores = []
    avgscores = []
    #Run each trial and update score matrix
    for i in range(numTrials):
        transition_priorities = []
        scores.append(pom_demo(1, m1, m2,m3,m4,m5,m6,m7))
    
    #Average each column in the score matrix
    for i in range(len(scores[0])):
        avgscores.append(avg2(scores, i))
        
    return avgscores
        
global PRINT_FLAG
PRINT_FLAG = False

global transition_priorities
transition_priorities = []
    
